﻿namespace Mueller.CoffeeMonitor

module Updates =
    open System
    open System.Deployment.Application
    open System.Windows.Forms
    open System.Threading

    type private ProgressForm(ad: ApplicationDeployment) as this =
        inherit Form(Text = "Downloading Update...", Width = 283, Height = 66, TopMost = true,
                     ShowInTaskbar = false, FormBorderStyle = FormBorderStyle.FixedToolWindow)

        let progress = new ProgressBar(Dock = DockStyle.Fill)

        let rec updateProgress (args: DeploymentProgressChangedEventArgs) =
            if progress.InvokeRequired then
                progress.Invoke(Action<_>(updateProgress), args) |> ignore
            else
                progress.Value <- args.ProgressPercentage

        let rec downloadComplete (args: ComponentModel.AsyncCompletedEventArgs) =
            if this.InvokeRequired then
                this.Invoke(Action<_>(downloadComplete), args) |> ignore
            else
                if args.Cancelled then
                    MessageBox.Show("The update of the CoffeeScript Monitor's latest version was cancelled.",
                                    "Update Error", MessageBoxButtons.OK, MessageBoxIcon.Error) |> ignore
                    this.Close()
                elif args.Error <> null then
                    MessageBox.Show(sprintf "ERROR: Could not install the latest version of CoffeeScript Monitor. Reason: \n%s\n\
                                             Please report this error at coffeemonitor.codeplex.com!" args.Error.Message,
                                    "Update Error", MessageBoxButtons.OK, MessageBoxIcon.Error) |> ignore
                    this.Close()
                else
                    let dr = MessageBox.Show("CoffeeScript Monitor has been updated. Restart? (If you do not restart now, \
                                              the new version will not take effect until after you quit and launch the application again.)",
                                             "Restart Application", MessageBoxButtons.OKCancel, MessageBoxIcon.Information)
                    if dr = DialogResult.OK then
                        Application.Restart() 

        do
            this.Controls.Add(progress)
            this.CenterToScreen()
            this.Load |> Observable.add (fun _ ->
                ad.UpdateProgressChanged |> Observable.add updateProgress
                ad.UpdateCompleted |> Observable.add downloadComplete
                ad.UpdateAsync())

            this.Closing |> Observable.add (fun _ ->
                ad.UpdateAsyncCancel())

    
    let rec private checkUpdate () = 
        let ctx = SynchronizationContext.Current
        async {
            if isNull AppDomain.CurrentDomain.ActivationContext || not ApplicationDeployment.IsNetworkDeployed then
                // this isn't a ClickOnce deployment
                return ()
            else
                let ad = ApplicationDeployment.CurrentDeployment
            
                try
                    // we could call CheckForUpdateAsync, but that just creates its own thread, and we're already on a threadpool thread here.
                    let info = ad.CheckForDetailedUpdate()
                    if info.UpdateAvailable then
                        do! Async.SwitchToContext ctx
                        if info.IsUpdateRequired then
                            MessageBox.Show("A mandatory update is available for CoffeeScript Monitor. We will install the update now,\
                                             after which we will save all of your in-progress data and restart the application.",
                                            "Update Available", MessageBoxButtons.OK, MessageBoxIcon.Warning) |> ignore
                            let progress = new ProgressForm(ad)
                            progress.Show()
                        else
                            let dr = MessageBox.Show("A CoffeeScript Monitor update is available. Would you like to update the application now?", 
                                                     "CoffeeScript Monitor", MessageBoxButtons.YesNo, MessageBoxIcon.Question)
                            if dr = DialogResult.Yes then
                                let progress = new ProgressForm(ad)
                                progress.Show()
                with
                | :? DeploymentDownloadException as dde ->
                    MessageBox.Show("The new version of CoffeeScript Monitor cannot be downloaded at this time.\n\n\
                                     Please check your network connection, or try again later.\nError: " + dde.Message,
                                    "Update Error", MessageBoxButtons.OK, MessageBoxIcon.Error) |> ignore
                | :? InvalidDeploymentException as ide ->
                    MessageBox.Show("Cannot check for a new version of CoffeeScript Monitor. The ClickOnce deployment is corrupt. \
                                     Please redeploy the application and try again.\nError: " + ide.Message,
                                    "Update Error", MessageBoxButtons.OK, MessageBoxIcon.Error) |> ignore
                | :? InvalidOperationException as ioe ->
                    MessageBox.Show("CoffeeScript Monitor cannot be updated. It is likely not a ClickOnce application.\nError: " + ioe.Message,
                                    "Update Error", MessageBoxButtons.OK, MessageBoxIcon.Error) |> ignore
        
                do! Async.Sleep (10 * 60 * 60 * 1000) // check every 10 hours
                return! checkUpdate()
        }

    let StartChecking () =
        checkUpdate() |> Async.Start